#ifdef WIN32
#include <io.h>
#else
#include <sys/file.h>
#include <stdio.h>
#endif
#include <iostream>
#include "miniutil/sharefile.h"
#include "miniutil/diru.h"
#include "miniutil/logservice.h"

#ifdef WIN32
#define FSEEK _fseeki64
#define FTELL _ftelli64
#else
#include <unistd.h>
//require to #define _FILE_OFFSET_BITS 64
//the better is to -D_FILE_OFFSET_BITS=64 in GCC complication
#define FSEEK fseeko
#define FTELL ftello
#endif

namespace miniutil { namespace frame {

//-----------------------------------------------------------------------------------
/*
    meta file
    0 1 2 3 4 5 6 7 8 9 A B C D E F
    [ length      ] [reserved     ]
    [ offset      ] [length       ]
    .....
*/
static fs_t Read8IntFromFile(FILE* pfile, fs_t& value)
{
    size_t ret;
    unsigned char buf[8];
    value = 0;
    ret = fread(buf, 8, 1, pfile);
    for(int i=0; i< 8; i++)
    {
        value += (((fs_t)buf[i])<<(56-i*8));
    }
    return 8;
}

static int Write8IntToFile(FILE* pfile, fs_t value)
{
    char buf[8];
    for(int i=0; i< 8; i++)
    {
        buf[i] = (char)((value>>(56-i*8))&0xff);
    }
    size_t ret = fwrite(buf, 8,1,pfile);
    fflush(pfile);
    return 8; 
}


LocalSharedFile::LocalSharedFile()
{
    mpdatafile = NULL;
    mpmetafile = NULL;
    mbopenpart = false;
    mlength = -1;
}

LocalSharedFile::~LocalSharedFile()
{
    Close();
}

int LocalSharedFile::Open(const std::string &flnm)
{
    if(mpmetafile != NULL)
        return -1;

    mfilepath = flnm;
    mmetapath = mfilepath+".meta";

    std::string lockpath = mfilepath+".lock";
    mplockfile = TryOpenOneFile(lockpath, "r");
    if(mplockfile == NULL)
        mplockfile = TryOpenOneFile(lockpath);
    if(mplockfile == NULL)
        return -1;

    mpmetafile = TryOpenOneFile(mmetapath, "r");
    if(mpmetafile == NULL)
    {
        mpmetafile = TryOpenOneFile(mmetapath);
        if(mpmetafile == NULL)
        {
            return -1;
        }

        Lock();
        fs_t mdatalen;
        mpdatafile = TryOpenOneFile(mfilepath, "r");
        if(mpdatafile != NULL)
        {
            FSEEK(mpdatafile, 0, SEEK_SET);
            mdatalen = FTELL(mpdatafile);

            Write8IntToFile(mpmetafile, mdatalen);
            Write8IntToFile(mpmetafile, 0x00);
            Write8IntToFile(mpmetafile, 0x00);
            Write8IntToFile(mpmetafile, mdatalen);
        }
        else
        {
            Write8IntToFile(mpmetafile, 0x00);
            Write8IntToFile(mpmetafile, 0x00);
            mpdatafile = TryOpenOneFile(mfilepath+".part");
            mbopenpart = true;
        }
        UnLock();
    }
    else
    {
        Lock();
        ReloadMetaInfo();
        mpdatafile = TryOpenOneFile(mfilepath.c_str(), "r");
        if(mpdatafile == NULL)
        {
            mpdatafile = TryOpenOneFile(mfilepath+".part");
            if(mpdatafile == NULL)
            {
                fclose(mpmetafile);
                UnLock();
                return -1;
            }
            mbopenpart = true;
        }
        UnLock();
    }

    return 0;

}


int LocalSharedFile::Close()
{
    std::cout<<"close..."<<std::endl;
    Lock();
    if(mpdatafile != NULL)
    {
        fclose(mpdatafile);
        mpdatafile = NULL;
    }

    if(mpmetafile != NULL)
        fclose(mpmetafile);

    mpmetafile = NULL;

    if(mbopenpart && IsComplete())
    {
        std::cout<<"completed!"<<std::endl;
        int ret = ::rename((mfilepath+std::string(".part")).c_str(), mfilepath.c_str());
        if(ret == -1)
        {
            perror(NULL);
            std::cout<<mfilepath+std::string(".part")<<":"<<mfilepath<<std::endl;
        }
    }

    mfilepath = "";
    mmetapath = "";
    miniutil::util::Scope<fs_t> emptyinfo;
    mexitinfo = emptyinfo;

    UnLock();
    
    //hwo to deal with about multi-process?
    /*
    if(IsComplete())
    {
        mmetapath = mfilepath+".meta";
        std::string lockpath = mfilepath+".lock";
        miniutil::fileu::remove(mmetapath);
        miniutil::fileu::remove(lockpath);
    }
    */
    return 0;
}

int LocalSharedFile::Read(char* buf, fs_t offset, int size)
{
    Lock();
    ReloadMetaInfo();
    int rret = 0;
    do
    {
        if(mexitinfo.hasScope(std::make_pair(offset, (fs_t)size)))
        {
            int ret = FSEEK(mpdatafile, offset, SEEK_SET);
            if(ret != 0x00)
            {
                rret = -1;
                break;
            }
            size_t sret = fread(buf, size, 1, mpdatafile);
            if(sret == 0x00)
            {
                rret = -1;
                break;
            }
            rret = size;
        }
    }while(0);
    UnLock();
    return (int)rret;
}

int LocalSharedFile::Write(char* buf, fs_t offset, int size)
{
    if(size <= 0)
        return 0;

    Lock();
    ReloadMetaInfo();

    int rret = 0;
    int ret = 0;

    //std::cout<<"LocalSharedFile::Write:"<<offset<<":"<<size<<std::endl;

    //allow override..
    //if(mexitinfo.hasScope(std::make_pair(offset, (fs_t)size)))
 //   {
 //       UnLock();
    //  return size;
 //   }

    do
    {
        //fill file to enough length
        ret = FSEEK(mpdatafile, 0, SEEK_END);
        fs_t curlen = FTELL(mpdatafile);
        if(curlen < offset)
        {
            //miniutil::msec64_t now = miniutil::date::currentMSec();
#ifdef WIN32
            _chsize(_fileno (mpdatafile), (int)offset);
#else
            ftruncate(fileno (mpdatafile), offset);
#endif
            //for(int i = 0; i< offset-curlen; i++)
            //  ret = fwrite("\0", 1, 1, mpdatafile);
            //std::cout<<"CREATE FILE TIME:"<<miniutil::date::currentMSec()-now<<std::endl;
        }

        ret = FSEEK(mpdatafile, offset, SEEK_SET);
        if(ret != 0x00)
        {
            rret = -1;
            break;
        }
        size_t sret = fwrite(buf, size, 1, mpdatafile);
        if(sret == 0x00)
        {
            rret = -1;
            break;
        }
        else
        {
            rret = size;
        }

        fflush(mpdatafile);

        //merge mexitinfo
        mexitinfo.addScope(std::make_pair(offset, (fs_t)size));
        FlushMetaInfo();

    }while(0);  
    UnLock();
    return (int)rret;
}

int LocalSharedFile::ExistInfo(std::list<std::pair<fs_t, fs_t> >& info)
{
    Lock();
    info = mexitinfo.getScopes();
    UnLock();
    return 0;
}

//------------...-------------
int LocalSharedFile::Remove()
{
    miniutil::auto_lock kk(&mtx);
    miniutil::fileu::remove(mmetapath);
    miniutil::fileu::remove(mfilepath);
    miniutil::fileu::remove((mfilepath+".part").c_str());
    return 0;
}

int LocalSharedFile::SetLength(fs_t len)
{
    if(mpmetafile == NULL)
    {
        return -1;
    }

    Lock();
    mlength = len;
    FSEEK(mpmetafile, 0, SEEK_SET);
    Write8IntToFile(mpmetafile, mlength);
    Write8IntToFile(mpmetafile, 0x00);
    UnLock();
    return 0;
}

fs_t LocalSharedFile::GetLength()
{
    return mlength;
}

//--------helper function-------------
FILE* LocalSharedFile::TryOpenOneFile(std::string filenm, std::string option)
{
    FILE* pfile = NULL;
    pfile = fopen(filenm.c_str(), "rb+");
    if(pfile == NULL)
    {
        if(option == "r")
            return NULL;
#ifdef WIN32
        std::string::size_type pos = filenm.find_last_of("/\\");
#else
        std::string::size_type pos = filenm.find_last_of("/");
#endif
        if(pos != std::string::npos)
        {
            std::string dirname = filenm.substr(0, pos);
            if(!dirname.empty())
            {
                miniutil::diru::mkdir(dirname);
            }
        }
        pfile = fopen(filenm.c_str(), "wb+");
    }
    return pfile;
}

int LocalSharedFile::Lock()
{
    if(mpmetafile == NULL)
        return -1;
//large grain scope lock
#ifdef WIN32
    HANDLE h = (HANDLE)_get_osfhandle (_fileno (mplockfile));
    OVERLAPPED ovlp;
    ovlp.Offset = 0;
    ovlp.OffsetHigh = 0;
    ovlp.hEvent = 0;
    LockFileEx(h,LOCKFILE_EXCLUSIVE_LOCK,0, 512 ,0, &ovlp);
#else
    flock(fileno(mplockfile), LOCK_EX);
#endif
    return 0;
}

int LocalSharedFile::UnLock()
{
    if(mpmetafile == NULL)
        return -1;
#ifdef WIN32
    HANDLE h = (HANDLE)_get_osfhandle (_fileno (mplockfile));
    UnlockFile(h, 0, 0, 512, 0);
#else
    flock(fileno(mplockfile), LOCK_UN);
#endif
    return 0;
}

int LocalSharedFile::FlushMetaInfo()
{
    if(mpmetafile == NULL)
        return -1;
    std::list<std::pair<fs_t, fs_t> > exitinfo = mexitinfo.getScopes();

    FSEEK(mpmetafile, 0, SEEK_SET);
    Write8IntToFile(mpmetafile, mlength);
    Write8IntToFile(mpmetafile, (fs_t)0x00);
    std::list<std::pair<fs_t, fs_t> >::iterator iter_lpff;
    for(iter_lpff = exitinfo.begin(); iter_lpff != exitinfo.end(); iter_lpff ++)
    {
        Write8IntToFile(mpmetafile, iter_lpff->first);
        Write8IntToFile(mpmetafile, iter_lpff->second);
    }
    unsigned int fsize = (unsigned int)(16+exitinfo.size()*16);

#ifdef WIN32
    _chsize_s(_fileno (mpmetafile), fsize);
#else
    ftruncate64(fileno(mpmetafile), fsize);
#endif
    return 0;
}

int LocalSharedFile::ReloadMetaInfo()
{
    if(mpmetafile == NULL)
        return -1;

    FSEEK(mpmetafile, 0, SEEK_END);
    fs_t metalen = FTELL(mpmetafile);
    FSEEK(mpmetafile, 0, SEEK_SET);

    if(metalen >= 16)
    {
        fs_t reserveddata;
        Read8IntFromFile(mpmetafile, mlength);
        Read8IntFromFile(mpmetafile, reserveddata);
    }

    int i = 1;
    //int offset = 0;
    //while((offset = (metalen- (int)i*2*sizeof(fs_t))) >= 16)
    while((metalen- (int)(i*2*sizeof(fs_t))) >= 16)
    {
        fs_t offset, length;
        Read8IntFromFile(mpmetafile, offset);
        Read8IntFromFile(mpmetafile, length);
        mexitinfo.addScope(std::make_pair(offset, length));
        i ++;
    }
    return 0;
}

bool LocalSharedFile::IsComplete()
{
    F_DEBUG("Check File Completed\n");
    std::list<std::pair<fs_t, fs_t> > exitinfo = mexitinfo.getScopes();
    std::list<std::pair<fs_t, fs_t> >::iterator iter_lpff;
    for(iter_lpff = exitinfo.begin(); iter_lpff != exitinfo.end(); iter_lpff ++)
    {
        std::cout<<iter_lpff->first<<":"<<iter_lpff->second<<std::endl;
    }

    if(mlength > 0x00 && exitinfo.size() == 1 && exitinfo.front().first == 0 && exitinfo.front().second == mlength)
    {
        std::cout<<"Complete!"<<std::endl;
        return true;
    }
    return false;

}

} }
